Skip to content

Conversation

@piquark6046
Copy link
Member

@piquark6046 piquark6046 commented Jan 14, 2026

Fix #39
Fix #42
Fix #40

@piquark6046 piquark6046 changed the title Resign repo structure and get domains list from multiple sources Redesign repo structure and get domains list from multiple sources Jan 14, 2026
Res.writeHead(403)
Res.end()
return
} else if (ShouldPreventHTTPResponse || !Fs.existsSync(`${process.cwd()}/dist/${Req.url?.substring(1)}`)) {

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI about 4 hours ago

In general, to fix uncontrolled path usage, normalize the user input, resolve it relative to a known safe root directory, and then verify that the resulting path is contained within that root. If the normalized path escapes the root, reject the request. For this server, the safe root is effectively ${process.cwd()}/dist. We should use path.resolve to join this root with the requested path, then ensure the resolved path starts with the root directory. This prevents directory traversal and absolute path attacks.

The best fix here is: (1) introduce an import of Node’s path module; (2) compute const distRoot = path.join(process.cwd(), 'dist') once per request (or outside the handler if appropriate); (3) take Req.url?.substring(1) || '' as the requested path segment, resolve it with path.resolve(distRoot, requestedPath), and (4) check that the resolved path starts with distRoot + path.sep or equals distRoot. If not, return 403. Then use this sanitized resolvedPath for both Fs.existsSync and Fs.readFileSync. This preserves existing functionality (files are still only served if in FileName, from loopback, and if they exist) while adding robust path validation.

Concretely in builder/source/utils/http-server.ts:

  • Add import * as Path from 'node:path' at the top.
  • Inside the request handler, compute const distRoot = Path.join(process.cwd(), 'dist') and const requestPath = Req.url?.substring(1) || ''.
  • Derive const resolvedPath = Path.resolve(distRoot, requestPath).
  • Before the FileName.includes/loopback/exists checks that depend on the path, add a guard: if !resolvedPath.startsWith(distRoot + Path.sep) && resolvedPath !== distRoot, respond with 403 and return.
  • Replace both occurrences of `${process.cwd()}/dist/${Req.url?.substring(1)}` with resolvedPath.

No additional helper methods are strictly necessary beyond the new Path import and local variables.

Suggested changeset 1
builder/source/utils/http-server.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/builder/source/utils/http-server.ts b/builder/source/utils/http-server.ts
--- a/builder/source/utils/http-server.ts
+++ b/builder/source/utils/http-server.ts
@@ -1,5 +1,6 @@
 import * as HTTP from 'node:http'
 import * as Fs from 'node:fs'
+import * as Path from 'node:path'
 
 function IsLoopBack(IP: string) {
   return IP === '127.0.0.1' || IP === '::1' || IP === '::ffff:127.0.0.1'
@@ -7,7 +8,18 @@
 
 export function RunDebugServer(Port: number, FileName: string[], ShouldPreventHTTPResponse: boolean) {
   const HTTPServer = HTTP.createServer((Req, Res) => {
-    if (!FileName.includes(Req.url?.substring(1) || '')) {
+    const distRoot = Path.join(process.cwd(), 'dist')
+    const requestPath = Req.url?.substring(1) || ''
+    const resolvedPath = Path.resolve(distRoot, requestPath)
+
+    // Ensure the resolved path stays within the dist root to prevent directory traversal
+    if (!resolvedPath.startsWith(distRoot + Path.sep) && resolvedPath !== distRoot) {
+      Res.writeHead(403)
+      Res.end()
+      return
+    }
+
+    if (!FileName.includes(requestPath)) {
       Res.writeHead(404)
       Res.end()
       return
@@ -15,13 +27,13 @@
       Res.writeHead(403)
       Res.end()
       return
-    } else if (ShouldPreventHTTPResponse || !Fs.existsSync(`${process.cwd()}/dist/${Req.url?.substring(1)}`)) {
+    } else if (ShouldPreventHTTPResponse || !Fs.existsSync(resolvedPath)) {
       Res.writeHead(503)
       Res.end('File not built yet.')
       return
     }
 
-    const Content = Fs.readFileSync(`${process.cwd()}/dist/${Req.url?.substring(1)}`, 'utf-8')
+    const Content = Fs.readFileSync(resolvedPath, 'utf-8')
     Res.writeHead(200, {
       'content-type': 'application/javascript; charset=utf-8',
       'content-length': new TextEncoder().encode(Content).byteLength.toString()
EOF
@@ -1,5 +1,6 @@
import * as HTTP from 'node:http'
import * as Fs from 'node:fs'
import * as Path from 'node:path'

function IsLoopBack(IP: string) {
return IP === '127.0.0.1' || IP === '::1' || IP === '::ffff:127.0.0.1'
@@ -7,7 +8,18 @@

export function RunDebugServer(Port: number, FileName: string[], ShouldPreventHTTPResponse: boolean) {
const HTTPServer = HTTP.createServer((Req, Res) => {
if (!FileName.includes(Req.url?.substring(1) || '')) {
const distRoot = Path.join(process.cwd(), 'dist')
const requestPath = Req.url?.substring(1) || ''
const resolvedPath = Path.resolve(distRoot, requestPath)

// Ensure the resolved path stays within the dist root to prevent directory traversal
if (!resolvedPath.startsWith(distRoot + Path.sep) && resolvedPath !== distRoot) {
Res.writeHead(403)
Res.end()
return
}

if (!FileName.includes(requestPath)) {
Res.writeHead(404)
Res.end()
return
@@ -15,13 +27,13 @@
Res.writeHead(403)
Res.end()
return
} else if (ShouldPreventHTTPResponse || !Fs.existsSync(`${process.cwd()}/dist/${Req.url?.substring(1)}`)) {
} else if (ShouldPreventHTTPResponse || !Fs.existsSync(resolvedPath)) {
Res.writeHead(503)
Res.end('File not built yet.')
return
}

const Content = Fs.readFileSync(`${process.cwd()}/dist/${Req.url?.substring(1)}`, 'utf-8')
const Content = Fs.readFileSync(resolvedPath, 'utf-8')
Res.writeHead(200, {
'content-type': 'application/javascript; charset=utf-8',
'content-length': new TextEncoder().encode(Content).byteLength.toString()
Copilot is powered by AI and may make mistakes. Always verify output.
return
}

const Content = Fs.readFileSync(`${process.cwd()}/dist/${Req.url?.substring(1)}`, 'utf-8')

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI about 4 hours ago

In general, the fix is to ensure that any path derived from user input is constrained to a safe root directory. That means: (1) normalize the combined path (root + user input) using path.resolve (and optionally fs.realpathSync), and (2) verify that the normalized path is still under the intended root (dist), rejecting the request otherwise. Additionally, avoid using the raw URL for filesystem access; use the validated/normalized path instead.

For this snippet, the best minimal fix without changing functionality is:

  • Introduce a constant DIST_ROOT that holds the absolute path to the dist directory, computed via path.resolve(process.cwd(), 'dist').
  • Import Node’s path module as import * as Path from 'node:path'.
  • For each usage of `${process.cwd()}/dist/${Req.url?.substring(1)}`, compute a safe path:
    • Take the requested path segment Req.url?.substring(1) || ''.
    • Normalize it with Path.normalize.
    • Resolve it against DIST_ROOT with Path.resolve(DIST_ROOT, requestedPath).
    • Optionally (more robustly) pass that resolved path through Fs.realpathSync in a try/catch to handle missing files cleanly.
    • Check that the final resolved path starts with DIST_ROOT + Path.sep (or is exactly DIST_ROOT) to ensure it doesn’t escape the root. If it does, respond with 403 and return.
  • Replace both Fs.existsSync(...) and Fs.readFileSync(...) to use this validated resolved path (safePath) instead of reconstructing the string each time.

Concretely, inside RunDebugServer:

  • Define const DIST_ROOT = Path.resolve(process.cwd(), 'dist') near the top of the function.
  • Inside the request handler, once you’ve passed the FileName.includes(...) and loopback checks, compute the safe path as described.
  • Use that safe path for existsSync and readFileSync.
  • If resolving or realpathSync throws or if the path check fails, respond with 403 (for traversal) or 503/404 as appropriate and return.

This keeps the existing behavior (only serving files in FileName, still returning 503 when the file is not built) while adding proper path safety.

Suggested changeset 1
builder/source/utils/http-server.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/builder/source/utils/http-server.ts b/builder/source/utils/http-server.ts
--- a/builder/source/utils/http-server.ts
+++ b/builder/source/utils/http-server.ts
@@ -1,13 +1,18 @@
 import * as HTTP from 'node:http'
 import * as Fs from 'node:fs'
+import * as Path from 'node:path'
 
 function IsLoopBack(IP: string) {
   return IP === '127.0.0.1' || IP === '::1' || IP === '::ffff:127.0.0.1'
 }
 
 export function RunDebugServer(Port: number, FileName: string[], ShouldPreventHTTPResponse: boolean) {
+  const DIST_ROOT = Path.resolve(process.cwd(), 'dist')
+
   const HTTPServer = HTTP.createServer((Req, Res) => {
-    if (!FileName.includes(Req.url?.substring(1) || '')) {
+    const rawPath = Req.url?.substring(1) || ''
+
+    if (!FileName.includes(rawPath)) {
       Res.writeHead(404)
       Res.end()
       return
@@ -15,13 +13,33 @@
       Res.writeHead(403)
       Res.end()
       return
-    } else if (ShouldPreventHTTPResponse || !Fs.existsSync(`${process.cwd()}/dist/${Req.url?.substring(1)}`)) {
+    }
+
+    const normalizedRequestedPath = Path.normalize(rawPath)
+    const resolvedPath = Path.resolve(DIST_ROOT, normalizedRequestedPath)
+
+    if (resolvedPath !== DIST_ROOT && !resolvedPath.startsWith(DIST_ROOT + Path.sep)) {
+      Res.writeHead(403)
+      Res.end()
+      return
+    }
+
+    let safePath: string
+    try {
+      safePath = Fs.realpathSync(resolvedPath)
+    } catch {
       Res.writeHead(503)
       Res.end('File not built yet.')
       return
     }
 
-    const Content = Fs.readFileSync(`${process.cwd()}/dist/${Req.url?.substring(1)}`, 'utf-8')
+    if (ShouldPreventHTTPResponse || !Fs.existsSync(safePath)) {
+      Res.writeHead(503)
+      Res.end('File not built yet.')
+      return
+    }
+
+    const Content = Fs.readFileSync(safePath, 'utf-8')
     Res.writeHead(200, {
       'content-type': 'application/javascript; charset=utf-8',
       'content-length': new TextEncoder().encode(Content).byteLength.toString()
EOF
@@ -1,13 +1,18 @@
import * as HTTP from 'node:http'
import * as Fs from 'node:fs'
import * as Path from 'node:path'

function IsLoopBack(IP: string) {
return IP === '127.0.0.1' || IP === '::1' || IP === '::ffff:127.0.0.1'
}

export function RunDebugServer(Port: number, FileName: string[], ShouldPreventHTTPResponse: boolean) {
const DIST_ROOT = Path.resolve(process.cwd(), 'dist')

const HTTPServer = HTTP.createServer((Req, Res) => {
if (!FileName.includes(Req.url?.substring(1) || '')) {
const rawPath = Req.url?.substring(1) || ''

if (!FileName.includes(rawPath)) {
Res.writeHead(404)
Res.end()
return
@@ -15,13 +13,33 @@
Res.writeHead(403)
Res.end()
return
} else if (ShouldPreventHTTPResponse || !Fs.existsSync(`${process.cwd()}/dist/${Req.url?.substring(1)}`)) {
}

const normalizedRequestedPath = Path.normalize(rawPath)
const resolvedPath = Path.resolve(DIST_ROOT, normalizedRequestedPath)

if (resolvedPath !== DIST_ROOT && !resolvedPath.startsWith(DIST_ROOT + Path.sep)) {
Res.writeHead(403)
Res.end()
return
}

let safePath: string
try {
safePath = Fs.realpathSync(resolvedPath)
} catch {
Res.writeHead(503)
Res.end('File not built yet.')
return
}

const Content = Fs.readFileSync(`${process.cwd()}/dist/${Req.url?.substring(1)}`, 'utf-8')
if (ShouldPreventHTTPResponse || !Fs.existsSync(safePath)) {
Res.writeHead(503)
Res.end('File not built yet.')
return
}

const Content = Fs.readFileSync(safePath, 'utf-8')
Res.writeHead(200, {
'content-type': 'application/javascript; charset=utf-8',
'content-length': new TextEncoder().encode(Content).byteLength.toString()
Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add https://mainichi.jp/ to supported target sites chanto.jp.net https://www.welt.de/

2 participants